using System;
using System.Net.Mime;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Mail;

namespace ICalendar
{

    //CASO EU ADICIONE VTODO, entre outros, o VCALENDAR deve ficar separado e ir adicionando events and todos

    //MINIMO DE UM VCAL
    //BEGIN:VCALENDAR
    //VERSION:2.0
    //PRODID:-//hacksw/handcal//NONSGML v1.0//EN
    //BEGIN:VEVENT
    //DTSTART:19970714T170000Z
    //DTEND:19970715T035959Z
    //SUMMARY:Bastille Day Party
    //END:VEVENT
    //END:VCALENDAR

    //TimeZoneInfo


    public class Calendar
    {
        public const string MIMEContentType = "text/calendar";

        //private void CreateCalendarResponse()
        //{
        //    //Response.ContentType = "text/calendar";
        //}
        //StringBuilder vCalendar = new StringBuilder();        

        public EventCollection Events { get; set; }
        public TodoCollection Todos { get; set; }
        public JournalCollection Journals { get; set; }
        public FreeBusyCollection FreeBusies { get; set; }
        public TimeZoneCollection TimeZones { get; set; }
        public string Name { get; set; }
        public string ProductId { get; set; }
        public string Description { get; set; }
        public Guid CalendarId { get; set; }
        public TimeZoneInfo TimeZone { get; set; }
        public string Method { get; set; }
        
        public Calendar(string method)
        {
            this.TimeZone = TimeZoneInfo.Local;
            this.Method = method;
            this.Events = new EventCollection();
            this.Todos = new TodoCollection();
            this.Journals = new JournalCollection();
            this.FreeBusies = new FreeBusyCollection();
            this.TimeZones = new TimeZoneCollection();
        }

        //public void ImportCalendar(string calendar)
        //{

        //}

        public override string ToString()
        {
            var icsFile = new StringBuilder();

            // Write the VCalendar header
            icsFile.Append(WriteHeader());

            // Write the timezone section and return the tz alias
            // String timeZone = WriteTimeZones();

            // Holds GUIDs for recurrence/exception mappings
            //var idToGuid = new Hashtable();


            // Loop through each item and write it to the file
            foreach (var calendarEvent in this.Events)
            {
                icsFile.Append(calendarEvent.ToString(this.Method));
            }
            
            icsFile.Append(WriteFooter());
            
            return icsFile.ToString();
        }

        //public static string CreateCalendar(string calendarName, string productId, string method, EventCollection events)
        //{
        //    var icsFile = new StringBuilder();

        //    // Write the VCalendar header
        //    icsFile.Append(WriteHeader(calendarName, null, productId, method, Guid.Empty, null));

        //    // Write the timezone section and return the tz alias
        //   // String timeZone = WriteTimeZones();

        //    // Holds GUIDs for recurrence/exception mappings
        //    //var idToGuid = new Hashtable();


        //    // Loop through each item and write it to the file
        //    foreach (var calendarEvent in events)
        //    {
        //       icsFile.Append(calendarEvent.ToString());
        //        //WriteEvent(item, timeZone, idToGuid);
        //    }

        //    // Wrap up the file
        //    icsFile.Append(Util.Write("END", "VCALENDAR"));

        //    return icsFile.ToString();
        //}

        //public AlternateView CreateMessageAlternateView()
        //{

        //    return new AlternateView(new MemoryStream(Encoding.UTF8.GetBytes(ToString())), new ContentType(Calendar.MIMEContentType));
        //}

        public MailMessage CreateMailMessage()
        {
            
            var message = new MailMessage();

            var contentType = new ContentType("text/calendar");
            contentType.Parameters.Add("METHOD", this.Method);
            contentType.Parameters.Add("name","meeting.ics");
            var alternateView = AlternateView.CreateAlternateViewFromString(ToString(), contentType);
            alternateView.TransferEncoding = TransferEncoding.SevenBit;            
            message.AlternateViews.Add(alternateView);
            return message;
        }

      
        private string WriteFooter()
        {
            var result = new StringBuilder();
            // Calendar Name
            result.Append(Util.Write("X-WR-CALNAME", this.Name));

            //Call Desc
            result.Append(Util.Write("X-WR-CALDESC", this.Description));

            //CALENDAR ID
            result.Append(Util.Write("X-WR-RELCALID", this.CalendarId != Guid.Empty ? this.CalendarId.ToString() : null));

            //CALENDAR TIMEZONE
            result.Append(Util.Write("X-WR-TIMEZONE", this.TimeZone.Id));

            // Wrap up the file
            result.Append(Util.Write("END", "VCALENDAR"));

            return result.ToString();
        }

        private string WriteHeader() //string calendarName, string description, string productID, string method, Guid calendarId, string timeZoneId)
        {
            var result = new StringBuilder();
            // vCalendar header
            result.Append(Util.Write("BEGIN", "VCALENDAR"));

            //Product id -- Product Id Name example: !!
            //PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN
            //PRODID:-//Lotus Development Corporation//NONSGML Notes 7.0//EN
            result.Append(Util.Write("PRODID", this.ProductId));

            result.Append(Util.Write("VERSION", "2.0"));

            //// Calendar scale -- only Gregorian is supported? (RFC 2445 Section 4.7.1)
            result.Append(Util.Write("CALSCALE", "GREGORIAN"));

            // Method
            result.Append(Util.Write("METHOD", this.Method.ToUpperInvariant()));
          
            return result.ToString();
        }




        //private DateTime TimeZoneStart(SPSystemTime tzRule, int yearOffset)
        //{
        //    // Start at day one of the month
        //    DateTime startDate = new DateTime(
        //        SPUtility.GetServerNow(SPContext.Current.Web).AddYears(yearOffset).Year,
        //        tzRule.Month,
        //        1,
        //        tzRule.Hour,
        //        tzRule.Minute,
        //        tzRule.Second);

        //    // Offset from the first ? to the first Mon/Tues/Wed, depending on the day we want to end up on
        //    int numToAdd = Convert.ToInt32(tzRule.DayOfWeek) - Convert.ToInt32(startDate.DayOfWeek);

        //    // Go to the first instance of the correct day of the week
        //    startDate = startDate.Add(new TimeSpan(numToAdd, 0, 0, 0));

        //    // Go to the correct occurrence of the day
        //    startDate = startDate.Add(new TimeSpan(7 * tzRule.Day, 0, 0, 0));

        //    return startDate;
        //}

        // Turns RecurrenceData options into value for which day of the month (-1, 1, 2, 3, 4)
        private static int GetWeekdayOfMonthValue(string options)
        {
            int i = 0;

            if (options.Contains(@"weekdayofmonth=""first"""))
                i = 1;
            else if (options.Contains(@"weekdayofmonth=""second"""))
                i = 2;
            else if (options.Contains(@"weekdayofmonth=""third"""))
                i = 3;
            else if (options.Contains(@"weekdayofmonth=""fourth"""))
                i = 4;
            else if (options.Contains(@"weekdayofmonth=""last"""))
                i = -1;

            return i;
        }


        // Turns RecurrenceData field XML into iCalendar recurrence string
        private String ParseRecurrence(String recurrence)
        {
            String recurValue;

            // Normalize the recurrence XML
            recurrence = recurrence.ToLower();

            // Build a regex to parse out the pattern
            Regex pattern = new Regex(@"<repeat><(?<FREQ>\w+)\s(?<OPTIONS>.*)/></repeat>");

            // Match against the XML
            Match match = pattern.Match(recurrence);

            // Pull out the recurrence type and associated options
            String freq = match.Groups["FREQ"].ToString().ToUpper();
            String options = match.Groups["OPTIONS"].ToString().ToLower();
            String byday = null;

            // Is there a weekly="true" flag in the options?
            if (freq.Equals("DAILY") && options.Contains(@"weekday=""true"""))
            {
                // There's no support for daily-weekday patterns in this format, so we have to do some mangling
                freq = "WEEKLY";
                byday = "MO,TU,WE,TH,FR";
            }

            // Put the frequency into the return value, but handle special cases for tricky month/year patterns
            if (freq.Equals("MONTHLYBYDAY"))
            {
                recurValue = "FREQ=MONTHLY";
            }
            else if (freq.Equals("YEARLYBYDAY"))
            {
                recurValue = "FREQ=YEARLY";
            }
            else
            {
                recurValue = "FREQ=" + freq;
            }

            // Build a regex to parse out the interval
            pattern = new Regex(@"(day|week|month|year)frequency=""(?<INTERVAL>\d+)""");

            // Match against the XML
            match = pattern.Match(options);

            // Pull out the interval
            String interval = match.Groups["INTERVAL"].ToString();

            // If we found an interval, add it to the recur pattern
            if (!interval.Equals(""))
            {
                recurValue = recurValue + ";INTERVAL=" + interval;
            }

            // Include the starting day of the week if we're a weekly pattern
            if (freq.Equals("WEEKLY"))
            {
                // Only set up the byday list if we didn't already mangle it to support weekday recurrences
                if (byday == null)
                {
                    // Initialize it so it's no longer null
                    byday = "";

                    // The list of day values we're going to scan for
                    String[] days = new String[] { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };

                    foreach (String d in days)
                    {
                        // Is this day set in the recurrence options?
                        if (options.Contains(d.ToLower() + @"=""true"""))
                        {
                            // Add a comma if we're sticking our value on the end of an existing one
                            if (!byday.Equals(""))
                            {
                                byday = byday + ",";
                            }

                            // Add it to the list
                            byday = byday + d;
                        }
                    }
                }

                // Add the day pattern to the recur pattern
                recurValue = recurValue + ";BYDAY=" + byday;

                // Build a regex to parse out the first day of the week
                pattern = new Regex(@"<firstdayofweek>(?<FIRST>\w\w)</firstdayofweek>");

                // Match against the XML
                match = pattern.Match(recurrence);

                // Pull out the first day of the week
                String first = match.Groups["FIRST"].ToString().ToUpper();

                // Add it to the recur pattern
                recurValue = recurValue + ";WKST=" + first;
            }

            // Parse out monthly-specific options
            if (freq.Equals("MONTHLY"))
            {
                // Build a regex to parse out the day in a "every N months on the Nth" pattern
                pattern = new Regex(@"\sday=""(?<BYDAY>\d+)""");

                // Match against the pattern options XML
                match = pattern.Match(options);

                // Pull out the by day option
                String byDay = match.Groups["BYDAY"].ToString().ToUpper();

                // Add it to the recur pattern
                if (!byDay.Equals(""))
                {
                    recurValue = recurValue + ";BYMONTHDAY=" + byDay;
                }
            }

            // Parse out yearly-specific options
            if (freq.Equals("YEARLY"))
            {
                // Build a regex to parse out the month in a "every N (month) Nrd" pattern
                pattern = new Regex(@"\smonth=""(?<BYMONTH>\d+)""");

                // Match against the pattern options XML
                match = pattern.Match(options);

                // Pull out the by month option
                String byMonth = match.Groups["BYMONTH"].ToString().ToUpper();

                // Add it to the recur pattern
                if (!byMonth.Equals(""))
                {
                    recurValue = recurValue + ";BYMONTH=" + byMonth;
                }
            }

            // Parse out more complex monthly-and-yearly-specific options
            if (freq.Equals("MONTHLYBYDAY") || freq.Equals("YEARLYBYDAY"))
            {
                // Parse out the weekdayOfMonth property into an index we can use
                int i = GetWeekdayOfMonthValue(options);

                // Is this a yearly pattern?
                if (freq.Equals("YEARLYBYDAY"))
                {
                    // Yes, so parse out the month that it's supposed to occur in
                    pattern = new Regex(@"\smonth=""(?<MONTH>\d+)""");

                    // Match against the XML
                    match = pattern.Match(options);

                    // Pull out the number of occurrences
                    String monthIndex = match.Groups["MONTH"].ToString();

                    // Add it to the recurrence pattern
                    recurValue = recurValue + ";BYMONTH=" + monthIndex;
                }

                // Is this a "the Nth day" pattern?
                if (options.StartsWith(@"day=""true"""))
                {
                    recurValue = recurValue + ";BYMONTHDAY=" + i;
                }

                // Is this a "the Nth weekday" pattern?
                else if (options.Contains(@"weekday=""true"""))
                {
                    recurValue = recurValue + ";BYDAY=MO,TU,WE,TH,FR;BYSETPOS=" + i;
                }

                // Is this a "the Nth weekend day" pattern?
                // NOTE: this only works if the month doesn't start on a Sunday.  Since the
                // spec doesn't seem to support this pattern, I have to make it up.
                else if (options.Contains(@"weekend_day=""true"""))
                {
                    recurValue = recurValue + ";BYDAY=SU,SA;BYSETPOS=" + i;
                }

                // This must be a "the Nth D (day name)" pattern, then...
                else
                {
                    // The list of day values we're going to scan for
                    String[] days = new String[] { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };

                    foreach (String d in days)
                    {
                        // Is this day set in the recurrence options?
                        if (options.Contains(d.ToLower() + @"=""true"""))
                        {
                            // It is -- and there can be only one of them -- so tack it on and exit the loop
                            recurValue = recurValue + ";BYDAY=" + i + d;
                            break;
                        }
                    }
                }
            }

            // Build a regex to parse out the number of occurrences
            pattern = new Regex(@"<repeatinstances>(?<REPEAT>\d+)</repeatinstances>");

            // Match against the XML
            match = pattern.Match(recurrence);

            // Pull out the number of occurrences
            String repeat = match.Groups["REPEAT"].ToString();

            // If we found a number of occurrences, add it to the recur pattern
            if (!repeat.Equals(""))
            {
                recurValue = recurValue + ";COUNT=" + repeat;
            }

            // Build a regex to parse out the end by date
            pattern = new Regex(@"<windowend>(?<ENDDATE>.+)</windowend>");

            // Match against the XML
            match = pattern.Match(recurrence);

            // Pull out the end date
            String endDate = match.Groups["ENDDATE"].ToString();

            // If we found a value, add it to the recur pattern
            if (!endDate.Equals(""))
            {
                DateTime endDT = Convert.ToDateTime(endDate);
                recurValue = recurValue + ";UNTIL=" + endDT.ToUniversalTime().ToString("yyyyMMddTHHmmssZ");
            }

            return recurValue;
        }
    }
}
    
    //    private String WriteTimeZones()
    //    {
    //        String year, month, day, hour, minute, second;

    //        // Get the timezone in the web
            
    //        //SPTimeZone tz = SPContext.Current.Web.RegionalSettings.TimeZone;

    //        // Get the timezone description
    //        //String tzid = "tz"; // Escape(tz.Description);

    //        // If the bias from 'now' to standard time is 0, then we're in standard time
    //        //System.Globalization.CultureInfo.CurrentCulture.Calendar
    //        bool isStandardTime = (tz.Information.StandardBias == 0);

    //        // Get the date that standard time began last and this year
    //        DateTime stdStartLastYear = TimeZoneStart(tz.Information.StandardDate, -1);
    //        DateTime stdStartThisYear = TimeZoneStart(tz.Information.StandardDate, 0);

    //        // Get the date that daylight time begins this year and next
    //        DateTime dayStartThisYear = TimeZoneStart(tz.Information.DaylightDate, 0);
    //        DateTime dayStartNextYear = TimeZoneStart(tz.Information.DaylightDate, 1);

    //        // Get the time that the server thinks it is
    //        DateTime now = SPUtility.GetServerNow(SPContext.Current.Web);

    //        // Get the date that standard time and daylight time begin
    //        DateTime standardBegin, daylightBegin;

    //        if (isStandardTime)
    //        {
    //            // If we're in standard time, we need to return when the current std time started.
    //            // This may have been just a few months ago (if we're in Nov/Dev), or last year
    //            // (if we're in Jan/Feb).  If the std time start date is ahead of us, then we want the one
    //            // for last year
    //            standardBegin = now.CompareTo(stdStartThisYear) <= 0 ? stdStartLastYear : stdStartThisYear;

    //            // If we're in standard time, we need to return when the next daylight time starts.
    //            // This may be in just a few months (if we're in Jan/Feb) or next year (if we're in Nov/Dec).
    //            // If we haven't gotten to the standard time start for this year yet, we want this year's daylight time.
    //            daylightBegin = now.CompareTo(stdStartThisYear) <= 0 ? dayStartThisYear : dayStartNextYear;
    //        }
    //        else
    //        {
    //            // We're in daylight time.  Std time start is always ahead of us.
    //            standardBegin = stdStartThisYear;

    //            // We're in daylight time.  It just started!
    //            daylightBegin = dayStartThisYear;
    //        }

    //        // Get the standard time bias (480 minutes -> 8 hours 'til UTC -> -8 hours behind)
    //        String standardBias = Convert.ToString((tz.Information.StandardBias == 0 ?
    //            tz.Information.Bias : tz.Information.Bias + tz.Information.StandardBias) / 60 * -1);

    //        // Pad correctly
    //        if (standardBias.Length == 1)
    //        {
    //            standardBias = standardBias.Insert(0, "+0");
    //        }
    //        else if ((standardBias.Length == 2) && (standardBias.Substring(0, 1).Equals("-")))
    //        {
    //            standardBias = standardBias.Insert(1, "0");
    //        }
    //        else if (standardBias.Length == 2)
    //        {
    //            standardBias = standardBias + "+";
    //        }

    //        standardBias = standardBias + "00";

    //        // Get the daylight time bias
    //        String daylightBias = Convert.ToString((tz.Information.DaylightBias == 0 ?
    //            tz.Information.Bias : tz.Information.Bias + tz.Information.DaylightBias) / 60 * -1);

    //        // Pad correctly
    //        if (daylightBias.Length == 1)
    //        {
    //            daylightBias = daylightBias.Insert(0, "+0");
    //        }
    //        else if ((daylightBias.Length == 2) && (daylightBias.Substring(0, 1).Equals("-")))
    //        {
    //            daylightBias = daylightBias.Insert(1, "0");
    //        }
    //        else if (daylightBias.Length == 2)
    //        {
    //            daylightBias = standardBias + "+";
    //        }

    //        daylightBias = daylightBias + "00";

    //        // Timezone header
    //        Write("BEGIN", "VTIMEZONE");

    //        // Timezone ID (to identify this timezone later in the document)
    //        Write("TimeZoneId", tzid);

    //        // Last modified
    //        Write("LAST-MODIFIED", DateTime.Now.ToUniversalTime().ToString("yyyyMMddTHHmmssZ"));

    //        // Standard time header
    //        Write("BEGIN", "STANDARD");

    //        // Date and time this timezone started (if we're currently in it) or will start (if we're not in it)
    //        Write("DTSTART", standardBegin.ToString("yyyyMMddTHHmmss"));

    //        // Timeshift during this timezone
    //        Write("TZOFFSETTO", standardBias);

    //        // Timeshift outside of this timezone -- ternary to match iCal output
    //        Write("TZOFFSETFROM", isStandardTime ? "+0000" : daylightBias);

    //        // Standard time footer
    //        Write("END", "STANDARD");

    //        // Daylight time header
    //        Write("BEGIN", "DAYLIGHT");

    //        // Date and time this timezone started (if we're currently in it) or will start (if we're not in it)
    //        Write("DTSTART", daylightBegin.ToString("yyyyMMddTHHmmss"));

    //        // Timeshift during this timezone
    //        Write("TZOFFSETTO", daylightBias);

    //        // Timeshift outside of this timezone
    //        Write("TZOFFSETFROM", !isStandardTime ? "+0000" : standardBias);

    //        // Daylight time footer
    //        Write("END", "DAYLIGHT");

    //        // Timezone footer
    //        Write("END", "VTIMEZONE");

    //        return tzid;
    //    }



    //private void Page_Load(Object Sender, EventArgs e)
    //{
    //    // Check the URL parameters and return the correct list, or error out if necessary
    //    SPList list = GetListFromUrl();
        
    //    // If we got null, return an error to the user
    //    if (list == null)
    //    {
    //        Response.ContentType = "text/html";
    //        Response.Write("<html><body><h1>Error</h1>Missing or incorrect query parameter: List.</body></html>");
    //        return;
    //    }

    //    // Set the correct content type for the document (now that we know it isn't an error)
    //    Response.ContentType = "text/calendar";

    //    // Write the VCalendar header
    //    WriteHeader(list);
        
    //    // Write the timezone section and return the tz alias
    //    String timeZone = WriteTimeZones();
        
    //    // Holds GUIDs for recurrence/exception mappings
    //    Hashtable idToGuid = new Hashtable();

    //    // Loop through each item and write it to the file
    //    foreach (SPListItem item in list.Items)
    //    {
    //        WriteEvent(item, timeZone, idToGuid);
    //    }
        
    //    // Wrap up the file
    //    Write("END", "VCALENDAR");
    //}

    //// Return the SPList object corresponding to the guid passed
    //// in the url "list" parameter.  Returns null if the url 
    //// parameter is missing or if no list with that guid can be found.
    //private SPList GetListFromUrl()
    //{
    //    // Check for the List URL parameter
    //    String listGuid = Request.Params["List"];

    //    // If we didn't pass one, return
    //    if (listGuid == null)
    //    {
    //        return null;
    //    }

    //    // Turn the passed-in guid into a real one
    //    Guid g = new Guid(listGuid);

    //    // Get the current SPContext object
    //    SPContext wssContext = SPContext.GetContext(Context);

    //    // Get the current web
    //    SPWeb web = wssContext.Web;

    //    SPList list;

    //    try
    //    {
    //        // Get the WSS list that corresponds to this guid
    //        list = web.Lists[g];
    //    }
    //    catch (SPException)
    //    {
    //        // If the list guid is incorrect, return
    //        return null;
    //    }

    //    return list;
    //}

   

    //private void WriteHeader(SPList list)
    //{
    //    // vCalendar header
    //    Write("BEGIN", "VCALENDAR");
    //    Write("VERSION", "2.0");

    //    // List name
    //    Write("X-WR-CALNAME", Escape(list.Title));

    //    // Product id -- ?
    //    //Write("PRODID", "");

    //    // Calendar scale -- only Gregorian is supported? (RFC 2445 Section 4.7.1)
    //    Write("CALSCALE", "GREGORIAN");

    //    // Method
    //    Write("METHOD", "PUBLISH");
    //}

    // Returns the day at which the timezone specified by tzRule began in the year (this + yearOffset)
    //private DateTime TimeZoneStart(SPSystemTime tzRule, int yearOffset)
    //{
    //    // Start at day one of the month
    //    DateTime startDate = new DateTime(
    //        SPUtility.GetServerNow(SPContext.Current.Web).AddYears(yearOffset).Year,
    //        tzRule.Month,
    //        1,
    //        tzRule.Hour,
    //        tzRule.Minute,
    //        tzRule.Second);

    //    // Offset from the first ? to the first Mon/Tues/Wed, depending on the day we want to end up on
    //    int numToAdd = Convert.ToInt32(tzRule.DayOfWeek) - Convert.ToInt32(startDate.DayOfWeek);

    //    // Go to the first instance of the correct day of the week
    //    startDate = startDate.Add(new TimeSpan(numToAdd, 0, 0, 0));

    //    // Go to the correct occurrence of the day
    //    startDate = startDate.Add(new TimeSpan(7 * tzRule.Day, 0, 0, 0));

    //    return startDate;
    //}
    
    //// Turns RecurrenceData options into value for which day of the month (-1, 1, 2, 3, 4)
    //private int GetWeekdayOfMonthValue(string options)
    //{
    //    int i = 0;
        
    //    if (options.Contains(@"weekdayofmonth=""first"""))
    //        i = 1;
    //    else if (options.Contains(@"weekdayofmonth=""second"""))
    //        i = 2;
    //    else if (options.Contains(@"weekdayofmonth=""third"""))
    //        i = 3;
    //    else if (options.Contains(@"weekdayofmonth=""fourth"""))
    //        i = 4;
    //    else if (options.Contains(@"weekdayofmonth=""last"""))
    //        i = -1;
        
    //    return i;
    //}
    

    //// Turns RecurrenceData field XML into iCalendar recurrence string
    //private String ParseRecurrence(String recurrence)
    //{
    //    String recurValue;

    //    // Normalize the recurrence XML
    //    recurrence = recurrence.ToLower();

    //    // Build a regex to parse out the pattern
    //    Regex pattern = new Regex(@"<repeat><(?<FREQ>\w+)\s(?<OPTIONS>.*)/></repeat>");

    //    // Match against the XML
    //    Match match = pattern.Match(recurrence);

    //    // Pull out the recurrence type and associated options
    //    String freq = match.Groups["FREQ"].ToString().ToUpper();
    //    String options = match.Groups["OPTIONS"].ToString().ToLower();
    //    String byday = null;

    //    // Is there a weekly="true" flag in the options?
    //    if (freq.Equals("DAILY") && options.Contains(@"weekday=""true"""))
    //    {
    //        // There's no support for daily-weekday patterns in this format, so we have to do some mangling
    //        freq = "WEEKLY";
    //        byday = "MO,TU,WE,TH,FR";
    //    }
        
    //    // Put the frequency into the return value, but handle special cases for tricky month/year patterns
    //    if (freq.Equals("MONTHLYBYDAY"))
    //    {
    //        recurValue = "FREQ=MONTHLY";
    //    }
    //    else if (freq.Equals("YEARLYBYDAY"))
    //    {
    //        recurValue = "FREQ=YEARLY";
    //    }
    //    else
    //    {
    //        recurValue = "FREQ=" + freq;
    //    }

    //    // Build a regex to parse out the interval
    //    pattern = new Regex(@"(day|week|month|year)frequency=""(?<INTERVAL>\d+)""");

    //    // Match against the XML
    //    match = pattern.Match(options);

    //    // Pull out the interval
    //    String interval = match.Groups["INTERVAL"].ToString();

    //    // If we found an interval, add it to the recur pattern
    //    if (!interval.Equals(""))
    //    {
    //        recurValue = recurValue + ";INTERVAL=" + interval;
    //    }
        
    //    // Include the starting day of the week if we're a weekly pattern
    //    if (freq.Equals("WEEKLY"))
    //    {
    //        // Only set up the byday list if we didn't already mangle it to support weekday recurrences
    //        if (byday == null)
    //        {
    //            // Initialize it so it's no longer null
    //            byday = "";

    //            // The list of day values we're going to scan for
    //            String[] days = new String[] { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };

    //            foreach (String d in days)
    //            {
    //                // Is this day set in the recurrence options?
    //                if (options.Contains(d.ToLower() + @"=""true"""))
    //                {
    //                    // Add a comma if we're sticking our value on the end of an existing one
    //                    if (!byday.Equals(""))
    //                    {
    //                        byday = byday + ",";
    //                    }

    //                    // Add it to the list
    //                    byday = byday + d;
    //                }
    //            }
    //        }

    //        // Add the day pattern to the recur pattern
    //        recurValue = recurValue + ";BYDAY=" + byday;
            
    //        // Build a regex to parse out the first day of the week
    //        pattern = new Regex(@"<firstdayofweek>(?<FIRST>\w\w)</firstdayofweek>");

    //        // Match against the XML
    //        match = pattern.Match(recurrence);

    //        // Pull out the first day of the week
    //        String first = match.Groups["FIRST"].ToString().ToUpper();

    //        // Add it to the recur pattern
    //        recurValue = recurValue + ";WKST=" + first;
    //    }
        
    //    // Parse out monthly-specific options
    //    if (freq.Equals("MONTHLY"))
    //    {
    //        // Build a regex to parse out the day in a "every N months on the Nth" pattern
    //        pattern = new Regex(@"\sday=""(?<BYDAY>\d+)""");

    //        // Match against the pattern options XML
    //        match = pattern.Match(options);

    //        // Pull out the by day option
    //        String byDay = match.Groups["BYDAY"].ToString().ToUpper();
            
    //        // Add it to the recur pattern
    //        if (!byDay.Equals(""))
    //        {
    //            recurValue = recurValue + ";BYMONTHDAY=" + byDay;
    //        }
    //    }

    //    // Parse out yearly-specific options
    //    if (freq.Equals("YEARLY"))
    //    {
    //        // Build a regex to parse out the month in a "every N (month) Nrd" pattern
    //        pattern = new Regex(@"\smonth=""(?<BYMONTH>\d+)""");

    //        // Match against the pattern options XML
    //        match = pattern.Match(options);

    //        // Pull out the by month option
    //        String byMonth = match.Groups["BYMONTH"].ToString().ToUpper();

    //        // Add it to the recur pattern
    //        if (!byMonth.Equals(""))
    //        {
    //            recurValue = recurValue + ";BYMONTH=" + byMonth;
    //        }
    //    }

    //    // Parse out more complex monthly-and-yearly-specific options
    //    if (freq.Equals("MONTHLYBYDAY") || freq.Equals("YEARLYBYDAY"))
    //    {
    //        // Parse out the weekdayOfMonth property into an index we can use
    //        int i = GetWeekdayOfMonthValue(options);

    //        // Is this a yearly pattern?
    //        if (freq.Equals("YEARLYBYDAY"))
    //        {
    //            // Yes, so parse out the month that it's supposed to occur in
    //            pattern = new Regex(@"\smonth=""(?<MONTH>\d+)""");

    //            // Match against the XML
    //            match = pattern.Match(options);

    //            // Pull out the number of occurrences
    //            String monthIndex = match.Groups["MONTH"].ToString();

    //            // Add it to the recurrence pattern
    //            recurValue = recurValue + ";BYMONTH=" + monthIndex;
    //        }

    //        // Is this a "the Nth day" pattern?
    //        if (options.StartsWith(@"day=""true"""))
    //        {
    //            recurValue = recurValue + ";BYMONTHDAY=" + i;
    //        }
            
    //        // Is this a "the Nth weekday" pattern?
    //        else if (options.Contains(@"weekday=""true"""))
    //        {
    //            recurValue = recurValue + ";BYDAY=MO,TU,WE,TH,FR;BYSETPOS=" + i;
    //        }

    //        // Is this a "the Nth weekend day" pattern?
    //        // NOTE: this only works if the month doesn't start on a Sunday.  Since the
    //        // spec doesn't seem to support this pattern, I have to make it up.
    //        else if (options.Contains(@"weekend_day=""true"""))
    //        {
    //            recurValue = recurValue + ";BYDAY=SU,SA;BYSETPOS=" + i;
    //        }

    //        // This must be a "the Nth D (day name)" pattern, then...
    //        else
    //        {
    //            // The list of day values we're going to scan for
    //            String[] days = new String[] { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };

    //            foreach (String d in days)
    //            {
    //                // Is this day set in the recurrence options?
    //                if (options.Contains(d.ToLower() + @"=""true"""))
    //                {
    //                    // It is -- and there can be only one of them -- so tack it on and exit the loop
    //                    recurValue = recurValue + ";BYDAY=" + i + d;
    //                    break;
    //                }
    //            }
    //        }
    //    }

    //    // Build a regex to parse out the number of occurrences
    //    pattern = new Regex(@"<repeatinstances>(?<REPEAT>\d+)</repeatinstances>");

    //    // Match against the XML
    //    match = pattern.Match(recurrence);

    //    // Pull out the number of occurrences
    //    String repeat = match.Groups["REPEAT"].ToString();

    //    // If we found a number of occurrences, add it to the recur pattern
    //    if (!repeat.Equals(""))
    //    {
    //        recurValue = recurValue + ";COUNT=" + repeat;
    //    }

    //    // Build a regex to parse out the end by date
    //    pattern = new Regex(@"<windowend>(?<ENDDATE>.+)</windowend>");

    //    // Match against the XML
    //    match = pattern.Match(recurrence);

    //    // Pull out the end date
    //    String endDate = match.Groups["ENDDATE"].ToString();

    //    // If we found a value, add it to the recur pattern
    //    if (!endDate.Equals(""))
    //    {
    //        DateTime endDT = Convert.ToDateTime(endDate);
    //        recurValue = recurValue + ";UNTIL=" + endDT.ToUniversalTime().ToString("yyyyMMddTHHmmssZ");
    //    }

    //    return recurValue;
    //}

    //private SPListItem GetFirstInstance(SPListItem item)
    //{
    //    DateTime itemStartDate = (DateTime)item["EventDate"];
    //    SPQuery query = new SPQuery();
    //    query.ExpandRecurrence = true;
    //    query.Query = "<Where><DateRangesOverlap><FieldRef Name=\"EventDate\" /><FieldRef Name=\"EndDate\" /><FieldRef Name=\"RecurrenceId\" /><Value Type=\"DateTime\"><Month /></Value></DateRangesOverlap></Where>";
    //    SPListItem i = null;
    //    int monthOffset = 0;
    //    SPListItemCollection allItems;

    //    // Look up to a little over a month ahead
    //    while (monthOffset < 14)
    //    {
    //        // Start from (start date + offset*30) and look ahead a month
    //        query.CalendarDate = ((DateTime)item["EventDate"]).AddMonths(monthOffset);
            
    //        // Get all the items
    //        allItems = item.ParentList.GetItems(query);
            
    //        // These items are sorted earliest to latest by start date
    //        foreach (SPListItem expandedItem in allItems)
    //        {
    //            // Did we find an instance that matches our series ID?
    //            if (expandedItem.ID == item.ID)
    //            {
    //                return expandedItem;
    //            }
    //        }

    //        // If we didn't find anything, look in the next month
    //        monthOffset++;
    //    }
        
    //    // Return null if we looked all over and couldn't find anything.
    //    return null;
    //}

    //private ArrayList GetDeletedInstancesOfSeries(SPListItem item)
    //{
    //    ArrayList deletedInstances = new ArrayList();

    //    // Get the ID of the master series
    //    int id = item.ID;
        
    //    // Loop through each item in the list
    //    foreach (SPListItem possibleItem in item.ParentList.Items)
    //    {
    //        // Find all items marked as a deleted recurrence item with a matching master series ID 
    //        if (((bool)possibleItem["Recurrence"])
    //            && (((Int32)possibleItem["Event Type"]) == 3)
    //            && (((int)possibleItem["MasterSeriesItemID"]) == id))
    //        {
    //            deletedInstances.Add(possibleItem);
    //        }
    //    }

    //    return deletedInstances;
    //}

    //private String WriteTimeZones()
    //{
    //    String year, month, day, hour, minute, second;
        
    //    // Get the timezone in the web
    //    SPTimeZone tz = SPContext.Current.Web.RegionalSettings.TimeZone;
        
    //    // Get the timezone description
    //    String tzid = "tz"; // Escape(tz.Description);

    //    // If the bias from 'now' to standard time is 0, then we're in standard time
    //    bool isStandardTime = (tz.Information.StandardBias == 0);

    //    // Get the date that standard time began last and this year
    //    DateTime stdStartLastYear = TimeZoneStart(tz.Information.StandardDate, -1);
    //    DateTime stdStartThisYear = TimeZoneStart(tz.Information.StandardDate, 0);

    //    // Get the date that daylight time begins this year and next
    //    DateTime dayStartThisYear = TimeZoneStart(tz.Information.DaylightDate, 0);
    //    DateTime dayStartNextYear = TimeZoneStart(tz.Information.DaylightDate, 1);

    //    // Get the time that the server thinks it is
    //    DateTime now = SPUtility.GetServerNow(SPContext.Current.Web);

    //    // Get the date that standard time and daylight time begin
    //    DateTime standardBegin, daylightBegin;

    //    if (isStandardTime)
    //    {
    //        // If we're in standard time, we need to return when the current std time started.
    //        // This may have been just a few months ago (if we're in Nov/Dev), or last year
    //        // (if we're in Jan/Feb).  If the std time start date is ahead of us, then we want the one
    //        // for last year
    //        standardBegin = now.CompareTo(stdStartThisYear) <= 0 ? stdStartLastYear : stdStartThisYear;

    //        // If we're in standard time, we need to return when the next daylight time starts.
    //        // This may be in just a few months (if we're in Jan/Feb) or next year (if we're in Nov/Dec).
    //        // If we haven't gotten to the standard time start for this year yet, we want this year's daylight time.
    //        daylightBegin = now.CompareTo(stdStartThisYear) <= 0 ? dayStartThisYear : dayStartNextYear;
    //    }
    //    else
    //    {
    //        // We're in daylight time.  Std time start is always ahead of us.
    //        standardBegin = stdStartThisYear;

    //        // We're in daylight time.  It just started!
    //        daylightBegin = dayStartThisYear;
    //    }

    //    // Get the standard time bias (480 minutes -> 8 hours 'til UTC -> -8 hours behind)
    //    String standardBias = Convert.ToString((tz.Information.StandardBias == 0 ?
    //        tz.Information.Bias : tz.Information.Bias + tz.Information.StandardBias) / 60 * -1);
        
    //    // Pad correctly
    //    if (standardBias.Length == 1)
    //    {
    //        standardBias = standardBias.Insert(0, "+0");
    //    }
    //    else if ((standardBias.Length == 2) && (standardBias.Substring(0, 1).Equals("-")))
    //    {
    //        standardBias = standardBias.Insert(1, "0");
    //    }
    //    else if (standardBias.Length == 2)
    //    {
    //        standardBias = standardBias + "+";
    //    }

    //    standardBias = standardBias + "00";

    //    // Get the daylight time bias
    //    String daylightBias = Convert.ToString((tz.Information.DaylightBias == 0 ?
    //        tz.Information.Bias : tz.Information.Bias + tz.Information.DaylightBias) / 60 * -1);

    //    // Pad correctly
    //    if (daylightBias.Length == 1)
    //    {
    //        daylightBias = daylightBias.Insert(0, "+0");
    //    }
    //    else if ((daylightBias.Length == 2) && (daylightBias.Substring(0, 1).Equals("-")))
    //    {
    //        daylightBias = daylightBias.Insert(1, "0");
    //    }
    //    else if (daylightBias.Length == 2)
    //    {
    //        daylightBias = standardBias + "+";
    //    }

    //    daylightBias = daylightBias + "00";
        
    //    // Timezone header
    //    Write("BEGIN", "VTIMEZONE");
        
    //    // Timezone ID (to identify this timezone later in the document)
    //    Write("TimeZoneId", tzid);

    //    // Last modified
    //    Write("LAST-MODIFIED", DateTime.Now.ToUniversalTime().ToString("yyyyMMddTHHmmssZ"));
        
    //    // Standard time header
    //    Write("BEGIN", "STANDARD");
        
    //    // Date and time this timezone started (if we're currently in it) or will start (if we're not in it)
    //    Write("DTSTART", standardBegin.ToString("yyyyMMddTHHmmss"));
        
    //    // Timeshift during this timezone
    //    Write("TZOFFSETTO", standardBias);
        
    //    // Timeshift outside of this timezone -- ternary to match iCal output
    //    Write("TZOFFSETFROM", isStandardTime ? "+0000" : daylightBias);
        
    //    // Standard time footer
    //    Write("END", "STANDARD");

    //    // Daylight time header
    //    Write("BEGIN", "DAYLIGHT");

    //    // Date and time this timezone started (if we're currently in it) or will start (if we're not in it)
    //    Write("DTSTART", daylightBegin.ToString("yyyyMMddTHHmmss"));

    //    // Timeshift during this timezone
    //    Write("TZOFFSETTO", daylightBias);

    //    // Timeshift outside of this timezone
    //    Write("TZOFFSETFROM", !isStandardTime ? "+0000" : standardBias);

    //    // Daylight time footer
    //    Write("END", "DAYLIGHT");

    //    // Timezone footer
    //    Write("END", "VTIMEZONE");
        
    //    return tzid;
    //}

    //private void WriteEvent(SPListItem item, String timeZone, Hashtable idToGuid)
    //{
    //    bool allDay = (bool)item["All Day Event"];
    //    bool recur = ((bool)item["Recurrence"]) && (((Int32)item["Event Type"]) == 1);
    //    bool exception = (((Int32)item["Event Type"]) == 4);
    //    bool deleted = (((Int32)item["Event Type"]) == 3);
    //    String guid = ((String)item["GUID"]).Trim(new char[] {'{', '}'});
    //    DateTime created = Convert.ToDateTime(item["Created"]);
    //    String url = SPContext.Current.Web.Url + item.ParentList.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=" + item.ID;
    //    DateTime start = Convert.ToDateTime(item["EventDate"]);
    //    DateTime end = Convert.ToDateTime(item["EndDate"]);
    //    String recurValue = null;
        
    //    // Skip deleted instances, since they get handled when the master series is written out
    //    if (deleted)
    //        return;

    //    // Special handling for recurrences
    //    if (recur)
    //    {
    //        Write("type", item["Event Type"]);
            
    //        // Get the item's recurrence XML
    //        String recurrence = ((String)item["RecurrenceData"]);

    //        // Parse it and generate a recurrence value
    //        recurValue = ParseRecurrence(recurrence);

    //        // We can't rely on the start date of the master, so get the first instance of the series
    //        SPListItem rItem = GetFirstInstance(item);

    //        // Did we find an instance?
    //        if (rItem == null)
    //        {
    //            // We couldn't find an instance of this recurrence, so let's fudge and call it a normal event
    //            recur = false;
    //        }
    //        else
    //        {
    //            // Correct the start date and calculate the end date by adding the duration to the start date
    //            start = Convert.ToDateTime(rItem["EventDate"]);
    //            end = start.AddSeconds((int)rItem["Duration"]);
    //        }
    //    }
        
    //    // Event header
    //    Write("BEGIN", "VEVENT");

    //    // Title
    //    Write("SUMMARY", Escape(item.Title));

    //    // Location
    //    Write("LOCATION", Escape(item["Location"]));

    //    // Description
    //    Write("DESCRIPTION", Escape(item.Fields["Description"].GetFieldValueAsText(item["Description"])));
        
    //    // Created date
    //    Write("DTSTAMP", created.ToString("yyyyMMddTHHmmssZ"));
        
    //    // Unique ID
    //    if (recur)
    //    {
    //        // Add the ID-to-GUID mapping for later, but only if it's a recurrence
    //        idToGuid.Add(item.ID, guid);
    //    }
    //    else if (exception)
    //    {
    //        // Replace this item's ID with the ID of the master series if it's an exception
    //        guid = (String)idToGuid[item["MasterSeriesItemID"]];
    //    }
        
    //    Write("UID", guid);
        
    //    // Sequence (aka version)
    //    Write("SEQUENCE", Convert.ToString(item["Version"]).Replace(".",""));

    //    // URL
    //    Write("URL;VALUE=URI", url);

    //    // Duration if we're an all-day event
    //    if (allDay)
    //    {
    //        // All-day events always end at :59, so we have to add one minute for this to work correctly
    //        Write("DURATION", "P" + ((TimeSpan)(end.AddMinutes(1) - start)).Days + "D");
    //    }
        
    //    // Start date (different format if all-day event)
    //    if (allDay)
    //    {
    //        Write("DTSTART;VALUE=DATE", start.ToString("yyyyMMdd"));
    //    }
    //    else
    //    {
    //        Write("DTSTART;TimeZoneId=" + timeZone, start.ToString("yyyyMMddTHHmmss"));
    //    }
 
    //    // End date (only if not an all-day event)
    //    if (!allDay)
    //    {
    //        Write("DTEND;TimeZoneId=" + timeZone, end.ToString("yyyyMMddTHHmmss"));
    //    }
        
    //    // Time of the instance we replaced, if we're an exception
    //    if (exception)
    //    {
    //        Write("RECURRENCE-ID;TimeZoneId=" + timeZone, (Convert.ToDateTime(item["Recurrence ID"])).ToString("yyyyMMddTHHmmss"));
    //    }

    //    // Recurrence rule and deleted instances
    //    if (recur)
    //    {
    //        Write("RRULE", recurValue);

    //        // Get all the deleted instances of this series
    //        ArrayList deletedInstances = GetDeletedInstancesOfSeries(item);

    //        // Loop through them
    //        foreach (SPListItem deletedInstance in deletedInstances)
    //        {
    //            // Write out an exception rule for each one
    //            Write("EXDATE;TimeZoneId=" + timeZone, (Convert.ToDateTime(deletedInstance["Recurrence ID"])).ToString("yyyyMMddTHHmmss"));
    //        }
    //    }

    //    // Event footer
    //    Write("END", "VEVENT");
    //}